/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package asfabdesk.presistencia;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.PersistenceException;
import org.hibernate.Query;
import org.hibernate.Session;

/**
 *
 * @author User
 */
public class DAO {
   
    private Session sessao;
    private boolean transacaoDoUsuario;
    


    public DAO() {
        inicializar();
    }

    private void inicializar() {
        if (sessao == null) {
            //System.out.println("Fabrica = " + HibernateUtil.getFabrica());
            sessao = HibernateUtil.getSession();
            //System.out.println("Sessao = " + sessao);
        }
    }

    public Session getSessao() {
        return sessao;
    }

    protected void setSessao(Session sessao) {
        this.sessao = sessao;
    }

    public <T> List<T> listar(Class classe) {
        return listar(classe, null);
    }

    public <T> List<T> listar(Class classe, String consulta, Object... parametros) {
        List<T> lista = new ArrayList();
        try {
            iniciarTransacaoInterna();
            Query q = null;
            if (consulta == null) {
                String hql = "SELECT o FROM " + classe.getSimpleName() + " o";
                for (Class i : classe.getInterfaces()) {
                    if (i == Undeleted.class) {
                        hql += " WHERE deleted = false";
                        break;
                    }
                }
                q = getQuery(hql);
            } else {
                q = getNamedQuery(consulta);
            }
            for (int i = 0; i < parametros.length; i++) {
                q.setParameter(i, parametros[i]);
            }

            //TODO: Tratar o atributo deleted das entidades Undeleted para as NamedQueries - talvez usar Criteria

            lista = (List<T>) q.list();
            finalizarTransacaoInterna();
        } catch (Exception ex) {
            desfazerTransacaoInterna();
            throw new PersistenceException("Falha ao localizar", ex);
        }
        return lista;
    }

    private Query getNamedQuery(String query) {
        return sessao.getNamedQuery(query);
    }

    private Query getQuery(String query) {
        return sessao.createQuery(query);
    }

    public <T> T localizar(Class classe, Long id) {
        return (T) sessao.get(classe, id);
    }

    public <T> T localizar(Class classe, String consulta, Object... parametros) {
        T objeto = null;
        try {
            iniciarTransacaoInterna();
            Query q = getNamedQuery(consulta);
            for (int i = 0; i < parametros.length; i++) {
                q.setParameter(i, parametros[i]);
            }
            objeto = (T) q.uniqueResult();
            finalizarTransacaoInterna();
        } catch (Exception ex) {
            desfazerTransacaoInterna();
            throw new PersistenceException("Falha ao localizar", ex);
        }
        return objeto;
    }

    public void restaurar(Object... objetos) {
        try {
            iniciarTransacaoInterna();
            for (Object o : objetos) {
                sessao.refresh(o);
            }
            finalizarTransacaoInterna();
        } catch (Exception ex) {
            desfazerTransacaoInterna();
            throw new PersistenceException("Falha ao restaurar", ex);
        }
    }

    public void restaurar(Collection colecao) {
        restaurar(colecao.toArray());
    }

    public void remover(Object... objetos) {
        try {
            iniciarTransacaoInterna();
            for (Object o : objetos) {
                if (o instanceof Undeleted) {
                    Undeleted undeleted = (Undeleted) o;
                    undeleted.setDeleted(true);
                    salvar(undeleted);
                } else {
                    sessao.delete(o);
                }
            }
            finalizarTransacaoInterna();
        } catch (Exception ex) {
            desfazerTransacaoInterna();
            throw new PersistenceException("Falha ao remover", ex);
        }
    }

    public void remover(Collection colecao) {
        remover(colecao.toArray());
    }

    public <T> T salvar(T objeto) {
        try {
            iniciarTransacaoInterna();
            T objetoSalvo = (T) sessao.merge(objeto);
            // Tentativa de injetar o id no objeto que foi salvo
            try {
                objeto.getClass().getMethod("setId", Long.class).invoke(objeto, objetoSalvo.getClass().getMethod("getId").invoke(objetoSalvo));
            } catch (Exception ex) {
//                ex.printStackTrace();
            }
            finalizarTransacaoInterna();
            return objetoSalvo;
        } catch (Exception ex) {
            desfazerTransacaoInterna();
            throw new PersistenceException("Falha ao salvar", ex);
        }
    }

    public void salvarTodos(Object... objetos) {
        try {
            begin();
            for (Object o : objetos) {
                salvar(o);
            }
            commit();
        } catch (Exception ex) {
            rollback();
            throw new PersistenceException("Falha ao salvar", ex);
        }
    }

    public void salvarTodos(Collection colecao) {
        salvarTodos(colecao.toArray());
    }

    public boolean estaEmTransacaoDoUsuario() {
        return transacaoDoUsuario;
    }

    public boolean naoEstaEmTransacaoDoUsuario() {
        return !transacaoDoUsuario;
    }

    public void begin() {
        iniciarTransacaoInterna();
        transacaoDoUsuario = true;
    }

    public void rollback() {
        transacaoDoUsuario = false;
        desfazerTransacaoInterna();
    }

    public void commit() {
        transacaoDoUsuario = false;
        finalizarTransacaoInterna();
    }

    private void fechar() {
        sessao.close();
        sessao = null;
    }

    private void iniciarTransacaoInterna() {
        if (naoEstaEmTransacaoDoUsuario()) {
            inicializar();
            if (!sessao.getTransaction().isActive()) {
                sessao.getTransaction().begin();
            }
        }
    }

    private void finalizarTransacaoInterna() {
        if (naoEstaEmTransacaoDoUsuario() && sessao.getTransaction().isActive()) {
            sessao.getTransaction().commit();
            fechar();
        }
    }

    private void desfazerTransacaoInterna() {
        if (naoEstaEmTransacaoDoUsuario() && sessao.getTransaction().isActive()) {
            sessao.getTransaction().rollback();
            fechar();
        }
    }
}

